package es.bwain.xprt.action;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.json.JSONObject;

import org.apache.commons.lang.StringUtils;
import org.jdom.Content;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.ProcessingInstruction;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

import es.bwain.xprt.errors.XprtError;

public class ActionServlet extends BaseServlet {

	private static final long serialVersionUID = -6966182368135654350L;

	@SuppressWarnings("unchecked")
	@Override
	public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		this.initVariables(request);
		
		Config configBall = new Config();
		configBall.setContextPath(request.getContextPath());
		configBall.setServletMapping(this.getServletMapping().substring(1));
		
		try {
			
			boolean xsltExists = false;
			URL xsltUrl = new URL(this.getXsltFile());
			if (((HttpURLConnection) xsltUrl.openConnection()).getResponseCode() == 200) {
				xsltExists = true;
			}
			
			Class action = Class.forName("es.bwain.xprt.action.DummyAction_");
			try {
				action = Class.forName(this.getXprtClass());
			} catch (ClassNotFoundException e) {
				if (!xsltExists) {
					throw e;
				}
			}
			
			Object instance = action.newInstance();
			
			Method init = action.getMethod("init", new Class[]{HttpServletRequest.class, HttpServletResponse.class});
			init.invoke(instance, new Object[]{request, response});
			
			
			populateFromRequest(request, action, instance);
			
			
			String methodName = "execute";
			if (request.getParameter(Initializer.getDirectToMethodParam()) != null) {
				methodName = request.getParameter(Initializer.getDirectToMethodParam());
			}
			
			if (instance instanceof DummyAction_ && !methodName.equals("execute")) {
				Class.forName(this.getXprtClass()); // This will throw an exception.
			}
			
			Method execute = action.getDeclaredMethod(methodName, new Class[]{HttpServletRequest.class, HttpServletResponse.class});
			execute.invoke(instance, new Object[]{request, response});
			
			ResponseFormat responseFormat = ResponseFormat.XML_AND_XSLT;
			if (request.getAttribute(Action.RESPONSE_FORMAT) != null) {
				responseFormat = (ResponseFormat) request.getAttribute(Action.RESPONSE_FORMAT);
			}
			
			if (request.getAttribute(Action.RESPONSE_REDIRECT) != null) {
				response.sendRedirect(request.getContextPath() + request.getAttribute(Action.RESPONSE_REDIRECT) + this.getServletMapping().substring(1));
			} else {
				
				Method putBall = action.getMethod("putBall", new Class[]{String.class, Object.class});
				putBall.invoke(instance, new Object[]{"form", instance});
				
				Method getAllBalls = action.getMethod("getAllBalls", new Class[]{});
				Hashtable balls = (Hashtable) getAllBalls.invoke(instance, new Object[]{});
	
				Document document = new Document();
				if (responseFormat == ResponseFormat.XML_AND_XSLT && xsltExists) {
					HashMap piMap = new HashMap(2);
					piMap.put("type", "text/xsl");
					piMap.put("href", xsltUrl.toExternalForm());
					ProcessingInstruction pi = new ProcessingInstruction("xml-stylesheet", piMap);
					document.addContent(0, pi);
				}
				
				if (responseFormat == ResponseFormat.XML_AND_XSLT ||
					responseFormat == ResponseFormat.XML_ONLY) {
					Iterator iter = balls.keySet().iterator();
					Element root = new Element("root");
					root.addContent(ActionServlet.XMLParse("config", configBall));
					while (iter.hasNext()) {
						String key = (String) iter.next();
						Object ball = balls.get(key);
						root.addContent(ActionServlet.XMLParse(key, ball));
					}
					document.addContent(root);
		
					Format format = Format.getPrettyFormat();
					org.jdom.output.XMLOutputter outputter = new XMLOutputter(format);
					outputter.output(document, response.getOutputStream());
					
				} else if (responseFormat == ResponseFormat.JSON) {
					
					JSONObject root = new JSONObject();
					ActionServlet.JSONParse("config", configBall, root);
					
					Iterator iter = balls.keySet().iterator();
					while (iter.hasNext()) {
						String key = (String) iter.next();
						Object ball = balls.get(key);
						ActionServlet.JSONParse(key, ball, root);
					}
					response.getOutputStream().write(root.toString().getBytes());
				}
			}
			
		} catch (ClassNotFoundException e) {
			XprtError.write(e, request, response, this.getXsltFile());
		} catch (SecurityException e) {
			XprtError.write(e, request, response);
		} catch (NoSuchMethodException e) {
			XprtError.write(e, request, response);
		} catch (IllegalArgumentException e) {
			XprtError.write(e, request, response);
		} catch (IllegalAccessException e) {
			XprtError.write(e, request, response);
		} catch (InvocationTargetException e) {
			XprtError.write(e, request, response);
		} catch (InstantiationException e) {
			XprtError.write(e, request, response);
		}
	
	}
	
	@SuppressWarnings("unchecked")
	public static void populateFromRequest(HttpServletRequest request, Class action, Object instance) throws NumberFormatException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException  {
		Method[] methods = action.getDeclaredMethods();
		int i;
		int methodsLength = methods.length;
		for (i=0; i < methodsLength; i++) {
			Method m = methods[i];
			if (m.getName().startsWith("set")) {
				String p = StringUtils.uncapitalize(m.getName().substring(3));
				String value = request.getParameter(p);
				if (value != null) {
					if (m.getParameterTypes().length == 1) {
						try {
							Class parameterType = m.getParameterTypes()[0];
							if (parameterType.isAssignableFrom(String.class)) {
								m.invoke(instance, value);
							} else if (parameterType.isAssignableFrom(Long.class)) {
								m.invoke(instance, Long.valueOf(value));
							}
						} catch (Exception ex) {
							
						}
					}
				}
			}
		}
	}

	@SuppressWarnings("unchecked")
	public static Element XMLParse(String key, Object ball) {
		Element element = new Element(key);
		if (ball instanceof ArrayList){
			List ballList = (ArrayList) ball;
			for (int i = 0; i < ballList.size(); i++) {
				Object child = ballList.get(i);
				String className = StringUtils.uncapitalize(child.getClass().getSimpleName());
				Content content = ActionServlet.XMLParse(className, ballList.get(i));
				element.addContent(content);
			}
		} else {
			Class clazz = ball.getClass();
			Method[] methods = clazz.getDeclaredMethods();
			for (int i = 0; i < methods.length; i++) {
				Method method = methods[i];
				if (method.getName().startsWith("get")){
					
					try {
						Element child = new Element(StringUtils.uncapitalize(method.getName().substring(3)));
						Object value = method.invoke(ball, new Object[]{});
						if (value != null) {
							if (value instanceof String){
								child.addContent((String)value);
								element.addContent(child);
							} else if (value instanceof Long) {
								child.addContent(String.valueOf((Long)value));
								element.addContent(child);
							} else {
								element.addContent(ActionServlet.XMLParse(StringUtils.uncapitalize(value.getClass().getSimpleName()), value));
							}
						}
						
					} catch (IllegalArgumentException e) {
						e.printStackTrace();
					} catch (IllegalAccessException e) {
						e.printStackTrace();
					} catch (InvocationTargetException e) {
						e.printStackTrace();
					}
				}
			}
		}
		return element;
	}

	@SuppressWarnings("unchecked")
	public static void JSONParse(String key, Object ball, JSONObject parent) {

		if (ball instanceof ArrayList){
			List ballList = (ArrayList) ball;
			for (int i = 0; i < ballList.size(); i++) {
				Object child = ballList.get(i);
				String className = StringUtils.uncapitalize(child.getClass().getSimpleName());
				JSONObject childObject = new JSONObject();
				ActionServlet.JSONParse(className, ballList.get(i), childObject);
				parent.accumulate(key, childObject);
			}
		} else {
			JSONObject jsonObject = new JSONObject();
			
			Class clazz = ball.getClass();
			Method[] methods = clazz.getDeclaredMethods();
			for (int i = 0; i < methods.length; i++) {
				Method method = methods[i];
				if (method.getName().startsWith("get")){
					
					try {
						String childKey = StringUtils.uncapitalize(method.getName().substring(3));
						Object value = method.invoke(ball, new Object[]{});
						if (value != null) {
							if (value instanceof String){
								jsonObject.put(childKey, value);
							} else if (value instanceof Long) {
								jsonObject.put(childKey, (Long) value);
							} else {
								ActionServlet.JSONParse(StringUtils.uncapitalize(value.getClass().getSimpleName()), value, jsonObject);
							}
						}
						
					} catch (IllegalArgumentException e) {
						e.printStackTrace();
					} catch (IllegalAccessException e) {
						e.printStackTrace();
					} catch (InvocationTargetException e) {
						e.printStackTrace();
					}
				}
			}
			parent.put(key, jsonObject);
		}
	}
	
}
